/***************************************************************************
 *   Copyright (C) 2005 by Joris Guisson                                   *
 *   joris.guisson@gmail.com                                               *
 *                                                                         *
 *   Copyright (C) 2007 Ivan Vasic                                         *
 *   ivasic@gmail.com                                                      *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.          *
 ***************************************************************************/
#include <string.h>
#include "sha1hashgen.h"

void Writequint32(quint8* buf, quint32 off, quint32 val)
{
	buf[off + 0] = (quint8)((val & 0xFF000000) >> 24);
	buf[off + 1] = (quint8)((val & 0x00FF0000) >> 16);
	buf[off + 2] = (quint8)((val & 0x0000FF00) >> 8);
	buf[off + 3] = (quint8)(val & 0x000000FF);
}

quint32 Readquint32(const quint8* buf, quint32 off)
{
	return (buf[off] << 24) | (buf[off+1] << 16) | (buf[off+2] << 8) | buf[off + 3];
}

static inline quint32 LeftRotate(quint32 x, quint32 n)
{
	return ((x << n) | ((x & 0xFFFFFFFF) >> (32 - n)));
}



SHA1HashGen::SHA1HashGen() : tmp_len(0), total_len(0)
{
}


SHA1HashGen::~SHA1HashGen()
{}

SHA1Hash SHA1HashGen::generate(const quint8* data, quint32 len)
{
	h0 = 0x67452301;
	h1 = 0xEFCDAB89;
	h2 = 0x98BADCFE;
	h3 = 0x10325476;
	h4 = 0xC3D2E1F0;

	quint32 num_64_byte_chunks = len / 64;
	quint32 left_over = len % 64;
	// proces regular data
	for (quint32 i = 0;i < num_64_byte_chunks;i++)
	{
		processChunk(data + (64*i));
	}

	// calculate the low and high byte of the data length
	quint32 total[2] = {0, 0};
	total[0] += len;
	total[0] &= 0xFFFFFFFF;

	if (total[0] < len)
		total[1]++;

	quint32 high = (total[0] >> 29) | (total[1] <<  3);
	quint32 low  = (total[0] <<  3);

	if (left_over == 0)
	{
		tmp[0] = 0x80;
		for (quint32 i = 1;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the padding
		processChunk(tmp);
	}
	else if (left_over < 56)
	{
		quint32 off = num_64_byte_chunks * 64;
		// copy left over bytes in tmp
		memcpy(tmp, data + off, left_over);
		tmp[left_over] = 0x80;
		for (quint32 i = left_over + 1;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the padding
		processChunk(tmp);
	}
	else
	{
		// now we need to process 2 chunks
		quint32 off = num_64_byte_chunks * 64;
		// copy left over bytes in tmp
		memcpy(tmp, data + off, left_over);
		tmp[left_over] = 0x80;
		for (quint32 i = left_over + 1;i < 64;i++)
			tmp[i] = 0;

		// process first chunk
		processChunk(tmp);

		for (quint32 i = 0;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the second chunk
		processChunk(tmp);
	}

	// construct final message
	quint8 hash[20];
	Writequint32(hash, 0, h0);
	Writequint32(hash, 4, h1);
	Writequint32(hash, 8, h2);
	Writequint32(hash, 12, h3);
	Writequint32(hash, 16, h4);

	return SHA1Hash(hash);
}



void SHA1HashGen::processChunk(const quint8* chunk)
{
	quint32 w[80];
	for (int i = 0;i < 80;i++)
	{
		if (i < 16)
		{
			w[i] = (chunk[4*i] << 24) |
				   (chunk[4*i + 1] << 16) |
				   (chunk[4*i + 2] << 8) |
				   chunk[4*i + 3];
		}
		else
		{
			w[i] = LeftRotate(w[i-3] ^ w[i-8] ^ w[i-14] ^ w[i-16], 1);
		}
	}

	quint32 a = h0;
	quint32 b = h1;
	quint32 c = h2;
	quint32 d = h3;
	quint32 e = h4;

	for (int i = 0;i < 80;i++)
	{
		quint32 f, k;
		if (i < 20)
		{
			f = (b & c) | ((~b) & d);
			k = 0x5A827999;
		}
		else if (i < 40)
		{
			f = b ^ c ^ d;
			k = 0x6ED9EBA1;
		}
		else if (i < 60)
		{
			f = (b & c) | (b & d) | (c & d);
			k = 0x8F1BBCDC;
		}
		else
		{
			f = b ^ c ^ d;
			k = 0xCA62C1D6;
		}

		quint32 temp = LeftRotate(a, 5) + f + e + k + w[i];
		e = d;
		d = c;
		c = LeftRotate(b, 30);
		b = a;
		a = temp;
	}
	h0 = (h0 + a) & 0xffffffff;
	h1 = (h1 + b) & 0xffffffff;
	h2 = (h2 + c) & 0xffffffff;
	h3 = (h3 + d) & 0xffffffff;
	h4 = (h4 + e) & 0xffffffff;
}


void SHA1HashGen::start()
{
	h0 = 0x67452301;
	h1 = 0xEFCDAB89;
	h2 = 0x98BADCFE;
	h3 = 0x10325476;
	h4 = 0xC3D2E1F0;
	tmp_len = total_len = 0;
	memset(tmp, 0, 64);
}

void SHA1HashGen::update(const quint8* data, quint32 len)
{
	if (tmp_len == 0)
	{
		quint32 num_64_byte_chunks = len / 64;
		quint32 left_over = len % 64;
		// proces data in chunks of 64 byte
		for (quint32 i = 0;i < num_64_byte_chunks;i++)
		{
			processChunk(data + (64*i));
		}

		if (left_over > 0)
		{
			// if there is anything left over, copy it in tmp
			memcpy(tmp, data + (64 * num_64_byte_chunks), left_over);
			tmp_len = left_over;
		}
		total_len += len;
	}
	else
	{

		if (tmp_len + len < 64)
		{
			// special case, not enough of data to fill tmp completely
			memcpy(tmp + tmp_len, data, len);
			tmp_len += len;
			total_len += len;
		}
		else
		{
			// copy start of data in tmp and process it
			quint32 off = 64 - tmp_len;
			memcpy(tmp + tmp_len, data, 64 - tmp_len);
			processChunk(tmp);
			tmp_len = 0;

			quint32 num_64_byte_chunks = (len - off) / 64;
			quint32 left_over = (len - off) % 64;

			for (quint32 i = 0;i < num_64_byte_chunks;i++)
			{
				processChunk(data + (off + (64*i)));
			}

			if (left_over > 0)
			{
				// if there is anything left over, copy it in tmp
				memcpy(tmp, data + (off + 64 * num_64_byte_chunks), left_over);
				tmp_len = left_over;
			}
			total_len += len;
		}
	}
}


void SHA1HashGen::end()
{
	// calculate the low and high byte of the data length
	quint32 total[2] = {0, 0};
	total[0] += total_len;
	total[0] &= 0xFFFFFFFF;

	if (total[0] < total_len)
		total[1]++;

	quint32 high = (total[0] >> 29) | (total[1] <<  3);
	quint32 low  = (total[0] <<  3);

	if (tmp_len == 0)
	{
		tmp[0] = 0x80;
		for (quint32 i = 1;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the padding
		processChunk(tmp);
	}
	else if (tmp_len < 56)
	{
		tmp[tmp_len] = 0x80;
		for (quint32 i = tmp_len + 1;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the padding
		processChunk(tmp);
	}
	else
	{
		// now we need to process 2 chunks
		tmp[tmp_len] = 0x80;
		for (quint32 i = tmp_len + 1;i < 56;i++)
			tmp[i] = 0;

		// process first chunk
		processChunk(tmp);

		for (quint32 i = 0;i < 56;i++)
			tmp[i] = 0;

		// put in the length as 64-bit integer (BIG-ENDIAN)
		Writequint32(tmp, 56, high);
		Writequint32(tmp, 60, low);
		// process the second chunk
		processChunk(tmp);
	}
}


SHA1Hash SHA1HashGen::get() const
	{
		// construct final message
		quint8 hash[20];
		Writequint32(hash, 0, h0);
		Writequint32(hash, 4, h1);
		Writequint32(hash, 8, h2);
		Writequint32(hash, 12, h3);
		Writequint32(hash, 16, h4);

		return SHA1Hash(hash);
	}
